7.4: Services

Contents:

In this chapter you learn about the different types of services, how to use them, and how to manage their lifecycles within your app.

What is a service?

A service is an application component that performs long-running operations, usually in the background. A service doesn't provide a user interface (UI). (An activity, on the other hand, provides a UI.)

A service can be started, bound, or both:

  • A started service is a service that an application component starts by calling startService().

    Use started services for tasks that run in the background to perform long-running operations. Also use started services for tasks that perform work for remote processes.

  • A bound service is a service that an application component binds to itself by calling bindService().

    Use bound services for tasks that another app component interacts with to perform interprocess communication (IPC). For example, a bound service might handle network transactions, perform file I/O, play music, or interact with a content provider.

Note: A service runs in the main thread of its hosting process—the service doesn't create its own thread and doesn't run in a separate process unless you specify that it should.

If your service is going to do any CPU-intensive work or blocking operations (such as MP3 playback or networking), create a new thread within the service to do that work. By using a separate thread, you reduce the risk of Application Not Responding (ANR) errors, and the application's main thread can remain dedicated to user interaction with your activities.

To implement any kind of service in your app:

  1. Declare the service in the manifest.
  2. Create implementation code, as described in Started services and Bound services, below.
  3. Manage the service lifecycle.

Declaring services in the manifest

As with activities and other components, you must declare all services in your application's manifest file. To declare a service, add a <service> element as a child of the <application> element. For example:

<manifest ... >
  ...
  <application ... >
      <service android:name="ExampleService"
               android:exported="false" />
      ...
  </application>
</manifest>

To block access to a service from other applications, declare the service as private. To do this, set the android:exported attribute to false. This stops other apps from starting your service, even when they use an explicit intent.

Started services

How a service starts:

  1. An application component such as an activity calls startService() and passes in an Intent. The Intent specifies the service and includes any data for the service to use.
  2. The system calls the service's onCreate() method and any other appropriate callbacks on the main thread. It's up to the service to implement these callbacks with the appropriate behavior, such as creating a secondary thread in which to work.
  3. The system calls the service's onStartCommand() method, passing in the Intent supplied by the client in step 1. (The client in this context is the application component that calls the service.)

Once started, a service can run in the background indefinitely, even if the component that started it is destroyed. Usually, a started service performs a single operation and does not return a result to the caller. For example, it might download or upload a file over the network. When the operation is done, the service should stop itself by calling stopSelf(), or another component can stop it by calling stopService().

For instance, suppose an activity needs to save data to an online database. The activity starts a companion service by passing an Intent to startService(). The service receives the intent in onStartCommand(), connects to the Internet, and performs the database transaction. When the transaction is done, the service uses stopSelf() to stop itself and is destroyed. (This is an example of a service you want to run in a worker thread instead of the main thread.)

IntentService

Most started services don't need to handle multiple requests simultaneously, and if they did it could be a dangerous multi-threading scenario. For this reason, it's probably best if you implement your service using the IntentService class.

IntentService is a useful subclass of Service:

  • IntentService automatically provides a worker thread to handle your Intent.
  • IntentService handles some of the boilerplate code that regular services need (such as starting and stopping the service).
  • IntentService can create a work queue that passes one intent at a time to your onHandleIntent() implementation, so you don't have to worry about multi-threading.

To implement IntentService:

  1. Provide a small constructor for the service.
  2. Create an implementation of onHandleIntent() to do the work that the client provides.

Here's an example implementation of IntentService:

public class HelloIntentService extends IntentService {
  /**
   * A constructor is required, and must call the super IntentService(String)
   * constructor with a name for the worker thread.
   */
  public HelloIntentService() {
      super("HelloIntentService");
  }

  /**
   * The IntentService calls this method from the default worker thread with
   * the intent that started the service. When this method returns, IntentService
   * stops the service, as appropriate.
   */
  @Override
  protected void onHandleIntent(Intent intent) {
      // Normally we would do some work here, like download a file.
      // For our sample, we just sleep for 5 seconds.
      try {
          Thread.sleep(5000);
      } catch (InterruptedException e) {
          // Restore interrupt status.
          Thread.currentThread().interrupt();
      }
  }
}

Bound services

A service is "bound" when an application component binds to it by calling bindService(). A bound service offers a client-server interface that allows components to interact with the service, send requests, and get results, sometimes using interprocess communication (IPC) to send and receive information across processes. A bound service runs only as long as another application component is bound to it. Multiple components can bind to the service at once, but when all of them unbind, the service is destroyed.

A bound service generally does not allow components to start it by calling startService().

Implementing a bound service

To implement a bound service, define the interface that specifies how a client can communicate with the service. This interface, which your service returns from the onBind() callback method, must be an implementation of IBinder.

To retrieve the IBinder interface, a client application component calls bindService(). Once the client receives the IBinder, the client interacts with the service through that interface.

There are multiple ways to implement a bound service, and the implementation is more complicated than a started service. For complete details about bound services, see Bound Services.

Binding to a service

To bind to a service that is declared in the manifest and implemented by an app component, use bindService() with an explicit Intent.

Caution: Do not use an implicit intent to bind to a service. Doing so is a security hazard, because you can't be certain what service will respond to your intent, and the user can't see which service starts. Beginning with Android 5.0 (API level 21), the system throws an exception if you call bindService() with an implicit Intent.

Service lifecycle

The lifecycle of a service is simpler than that of an activity. However, it's even more important that you pay close attention to how your service is created and destroyed. Because a service has no UI, services can continue to run in the background with no way for the user to know, even if the user switches to another application. This consumes resources and drains battery.

Like an activity, a service has lifecycle callback methods that you can implement to monitor changes in the service's state and perform work at the appropriate times. The following skeleton service demonstrates each of the lifecycle methods:

public class ExampleService extends Service {
    int mStartMode;       // indicates how to behave if the service is killed
    IBinder mBinder;      // interface for clients that bind
    boolean mAllowRebind; // indicates whether onRebind should be used

    @Override
    public void onCreate() {
        // The service is being created
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // The service is starting, due to a call to startService()
        return mStartMode;
    }
    @Override
    public IBinder onBind(Intent intent) {
        // A client is binding to the service with bindService()
        return mBinder;
    }
    @Override
    public boolean onUnbind(Intent intent) {
        // All clients have unbound with unbindService()
        return mAllowRebind;
    }
    @Override
    public void onRebind(Intent intent) {
        // A client is binding to the service with bindService(),
        // after onUnbind() has already been called
    }
    @Override
    public void onDestroy() {
        // The service is no longer used and is being destroyed
    }
}

Lifecycle of started services vs. bound services

A bound service exists only to serve the application component that's bound to it, so when no more components are bound to the service, the system destroys it. Bound services don't need to be explicitly stopped the way started services do (using stopService() or stopSelf()).

The diagram below shows a comparison between the started and bound service lifecycles. Started and Bound Service Lifecycle

Foreground services

While most services run in the background, some run in the foreground. A foreground service is a service that the user is aware of, so it's not a candidate for the system to kill when low on memory.

For example, a music player that plays music from a service should be set to run in the foreground, because the user is aware of its operation. The notification in the status bar might indicate the current song and allow the user to launch an activity to interact with the music player.

To request that a service run in the foreground, call startForeground() instead of startService(). This method takes two parameters: an integer that uniquely identifies the notification and the Notification for the status bar. This notification is ongoing, meaning that it can't be dismissed. It stays in the status bar until the service is stopped or removed from the foreground.

For example:

NotificationCompat.Builder mBuilder =
        new NotificationCompat.Builder(this)
        .setSmallIcon(R.drawable.notification_icon)
        .setContentTitle("My notification")
        .setContentText("Hello World!");
startForeground(ONGOING_NOTIFICATION_ID, mBuilder.build());
Note: The integer ID you give to startForeground() must not be 0.

To remove the service from the foreground, call stopForeground(). This method takes a boolean, indicating whether to remove the status bar notification. This method doesn't stop the service. However, if you stop the service while it's still running in the foreground, then the notification is also removed.

Scheduled services

For API level 21 and higher, you can launch services using the JobScheduler API. To use JobScheduler, you need to register jobs and specify their requirements for network and timing. The system schedules jobs for execution at appropriate times.

The JobScheduler interface provides many methods to define service-execution conditions. For details, see the JobScheduler reference.

Learn more

results matching ""

    No results matching ""